/*
	ahci_FAT32.c

	contains the FAT32 filesystem wrapper for the boot AHCI system

	Author: Aidan Goddard 28/2/14
*/

#include"ahci.h"

//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
// global tables and control variables

struct __attribute__((__packed__)) MBR_PART_TABLE
{
	uint8_t		boot_indicator;
	uint8_t 	start_head;
	uint16_t	start_sector : 6;
	uint16_t 	start_cylinder : 10;
	uint8_t 	system_ID;
	uint8_t 	end_head;
	uint16_t 	end_sector : 6;
	uint16_t 	end_cylinder : 10;
	uint32_t 	start_LBA;
	uint32_t 	total_sectors;
};

struct __attribute__((__packed__)) FAT32_BPB
{
	// base FAT fields
	uint8_t		BS_jmp_boot[3];
	uint8_t 	BS_OEM_name[8];
	uint16_t	BPB_bytes_per_sector;
	uint8_t 	BPB_sectors_per_cluster;
	uint16_t 	BPB_reserved_sectors;
	uint8_t 	BPB_number_of_FATs;
	uint16_t 	BPB_root_entry_count;
	uint16_t 	BPB_total_sectors_16;
	uint8_t 	BPB_media;
	uint16_t	BPB_FAT_size_16;
	uint16_t 	BPB_sectors_per_track;
	uint16_t 	BPB_number_of_heads;
	uint32_t 	BPB_hidden_sectors;
	uint32_t 	BPB_total_sectors_32;

	// FAT32 specific fields
	uint32_t 	BPB_FAT_size_32;
	uint16_t 	BPB_extended_flags;
	uint16_t	BPB_FS_version;
	uint32_t 	BPB_root_cluster;
	uint16_t 	BPB_FS_info;
	uint16_t 	BPB_backup_boot_sector;
	uint8_t 	BPB_reserved[12];
	uint8_t 	BS_drive_number;
	uint8_t 	BS_reserved_1;
	uint8_t 	BS_boot_sig;
	uint32_t 	BS_volume_ID;
	uint8_t 	BS_volume_label[11];
	uint8_t 	BS_FS_type[8];
};

struct __attribute__((__packed__)) FAT32_DIR
{
	char		DIR_name[11];
	uint8_t 	DIR_attributes;
	uint8_t		DIR_NT_reserved;
	uint8_t		DIR_creation_time_tenth;
	uint16_t 	DIR_creation_time;
	uint16_t 	DIR_creation_date;
	uint16_t 	DIR_last_accessed;
	uint16_t 	DIR_first_cluster_high;
	uint16_t 	DIR_write_time;
	uint16_t 	DIR_write_date;
	uint16_t 	DIR_first_cluster_low;
	uint32_t	DIR_file_size;
};

#define ATTR_READ_ONLY 		0x01
#define ATTR_HIDDEN			0x02
#define ATTR_SYSTEM			0x04
#define ATTR_VOLUME_ID		0x08
#define ATTR_DIRECTORY		0x10
#define ATTR_ARCHIVE		0x20
#define ATTR_LONG_NAME		0x0f

struct __attribute__((__packed__)) FAT32_LONG_NAME
{
	uint8_t 	LDIR_order;
	uint16_t	LDIR_name1[5];
	uint8_t 	LDIR_attributes;
	uint8_t 	LDIR_type;
	uint8_t		LDIR_checksum;
	uint16_t 	LDIR_name2[6];
	uint16_t 	LDIR_first_cluster_low;
	uint16_t 	LDIR_name3[2];
};

#define LAST_LONG_ENTRY		0x40

union FAT32_DIR_LONG_NAME
{
	struct FAT32_DIR dir;
	struct FAT32_LONG_NAME long_name;
};

static uint64_t FAT_area_start = 0;
static uint64_t data_area_start;
static uint32_t sectors_per_cluster;
static uint32_t root_cluster;



//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
// init function

uint32_t FAT32_INIT()
{

	// place to put read in data
	char tmp_buff[8192];

	// read in MBR
	AHCI_BOOT_READ((tmp_buff - KOFFSET), 0, 1);

	// find the boot partition
	struct MBR_PART_TABLE *part_table = (struct MBR_PART_TABLE*)(tmp_buff + 446);
	int i;
	for(i = 0; i < 4; i++)
	{
		if(part_table[i].boot_indicator & 0x80)
		{
			break;
		}
	}

	// i indicates which entry to use
	// get start LBA
	uint64_t LBA_offset = (uint64_t)part_table[i].start_LBA;

	// read in the partition's first sector (gets BPB for FAT32 partition)
	AHCI_BOOT_READ((tmp_buff - KOFFSET), LBA_offset, 1);
	struct FAT32_BPB *bpb = (struct FAT32_BPB*)(tmp_buff);

	// determine the FAT type (12, 16, 32)
	uint32_t root_dir_sectors = ((bpb->BPB_root_entry_count * 32) + (bpb->BPB_bytes_per_sector - 1)) / bpb->BPB_bytes_per_sector;	// should be zero for FAT32
	uint32_t FAT_size, total_sectors;
	if(bpb->BPB_FAT_size_16 != 0)
	{
		FAT_size = bpb->BPB_FAT_size_16;
	}
	else
	{
		FAT_size = bpb->BPB_FAT_size_32;
	}

	if(bpb->BPB_total_sectors_16 != 0)
	{
		total_sectors = bpb->BPB_total_sectors_16;
	}
	else
	{
		total_sectors = bpb->BPB_total_sectors_32;
	}
	uint32_t data_sectors = total_sectors - (bpb->BPB_reserved_sectors + (bpb->BPB_number_of_FATs * FAT_size) + root_dir_sectors);
	uint32_t cluster_count = data_sectors / bpb->BPB_sectors_per_cluster;

	uint32_t FAT_type;
	if(cluster_count < 4085)
	{
		return FAT32_IS_FAT_12;
	}
	else if(cluster_count < 65525)
	{
		return FAT32_IS_FAT_16;
	}
	else
	{
		FAT_type = 32;
	}

	// get first sector of FAT
	FAT_area_start = bpb->BPB_reserved_sectors + LBA_offset;

	// get first sector of data area
	data_area_start = FAT_area_start + (bpb->BPB_number_of_FATs * FAT_size);

	// get sectors per cluster
	sectors_per_cluster = bpb->BPB_sectors_per_cluster;

	// get root cluster
	root_cluster = bpb->BPB_root_cluster;

	// check cluster size
	if(sectors_per_cluster > 16)
	{
		return FAT32_CLUSTER_TOO_LARGE;
	}

	// finished
	return FAT32_SUCCESS;
}


//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
// internal function to 'decode' a short name entry
// the long_name variable should point to a 13-byte char array

static void GetLongFromShort(struct FAT32_DIR *dir, char *long_name)
{
	// short names are only used if all uppercase (or numbers)
	// if up to 8 chars followed by (maybe) up to 3 extension chars (with a dot '.' in the middle)
	// get first 8 chars
	int i;
	for(i = 0; i < 8; i++)
	{
		if(dir->DIR_name[i] == 0x20)	// if it's a space
		{
			break;
		}
		else
		{
			long_name[i] = dir->DIR_name[i];
		}
	}

	// check 9th char
	if(dir->DIR_name[8] != 0x20)
	{
		long_name[i++] = '.';
		long_name[i++] = dir->DIR_name[8];
		long_name[i++] = dir->DIR_name[9];
		long_name[i++] = dir->DIR_name[10];
	}

	// trailing null-char
	long_name[i] = 0;

	// finished
	return;
}


//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
// get dir count function

// internal recursive function to find the starting cluster number for the topmost directory in "path"
static uint32_t GetClusterForDir(char *path, uint32_t cluster_to_search)
{
	// ignore leading '/' or '\'
	if(path[0] == '\\' || path[0] == '/')
	{
		path++;
	}

	// check if at end of path string
	if(path[0] == 0x00)
	{
		// are at end of it, return this cluster as the one to search in
		return cluster_to_search;
	}

	// get length of name to search for
	uint32_t l;
	for(l = 0; (path[l] != 0 && path[l] != '\\' && path[l] != '/'); l++);

	// generate pointer to next path level
	char *next_path_level = (char*)&(path[l]);

	// chain control vars
	uint32_t in_chain = 0;
	uint32_t chain_total = 0;
	uint32_t chain_current = 0;
	uint32_t chain_name_index = 0;
	char chain_name[256];

	// buffers and control vars
	char clu_buffer[8192];
	char FAT_buffer[8192];
	uint32_t current_FAT_sector_start = (cluster_to_search / (128 * 16)) * 16;

	// read in first chunk of FAT table
	AHCI_BOOT_READ((FAT_buffer - KOFFSET), FAT_area_start + current_FAT_sector_start, 16);

	// each cluster searched
	while(1)
	{
		// read in the data cluster
		uint64_t sector_start = ((cluster_to_search - 2) * sectors_per_cluster) + data_area_start;
		AHCI_BOOT_READ((clu_buffer - KOFFSET), sector_start, sectors_per_cluster);

		// search through each directory entry in this cluster
		union FAT32_DIR_LONG_NAME *dirs = (union FAT32_DIR_LONG_NAME*)clu_buffer;
		int i;
		int dir_count = sectors_per_cluster * 16;
		for(i = 0; i < dir_count; i++)
		{
			// check if entry is in use
			if(dirs[i].dir.DIR_name[0] == 0x00 || (dirs[i].dir.DIR_name[0] & 0xff) == 0xe5 || dirs[i].dir.DIR_attributes == ATTR_VOLUME_ID)
			{
				// entry not in use
				continue;
			}

			// entry is in use
			// check if currently in the middle of a long-name chain
			if(in_chain)
			{
				// is in the middle of a long name chain
				// check chain_current matches value in entry and that it is a long name entry
				if(dirs[i].long_name.LDIR_order == chain_current && dirs[i].long_name.LDIR_attributes == ATTR_LONG_NAME)
				{
					// is the next long name entry
					// get the chars
					// don't need to check for padding & null-char as these will only be present in the last long-name entry
					// name chars 3
					int j;
					for(j = 1; j >= 0; j--)
					{
						// get the char
						uint16_t c = dirs[i].long_name.LDIR_name3[j];
						chain_name[chain_name_index--] = (char)(c & 0xff);
					}

					// name chars 2
					for(j = 5; j >= 0; j--)
					{
						// get the char
						uint16_t c = dirs[i].long_name.LDIR_name2[j];
						chain_name[chain_name_index--] = (char)(c & 0xff);
					}

					// name chars 1
					for(j = 4; j >= 0; j--)
					{
						// get the char
						uint16_t c = dirs[i].long_name.LDIR_name1[j];
						chain_name[chain_name_index--] = (char)(c & 0xff);
					}

					// have all name chars from this entry
					// decrease chain_current to point to next entry (or zero for last one)
					chain_current--;
				}
				else if(chain_current == 0 && dirs[i].long_name.LDIR_attributes != ATTR_LONG_NAME)
				{
					// is the associated short name entry
					char *long_name_to_check = (char*)&(chain_name[chain_name_index + 1]);
					in_chain = 0;

					// check it is the correct one (compare first l chars)
					int k;
					for(k = 0; k < l; k++)
					{
						// check char is equal
						if(long_name_to_check[k] != path[k])
						{
							break;
						}
					}

					// check if names match and it is a directory
					if(k == l  && long_name_to_check[k] == 0 && (dirs[i].dir.DIR_attributes & ATTR_DIRECTORY))
					{
						// is correct
						// get cluster number of this entry
						uint32_t new_cluster = dirs[i].dir.DIR_first_cluster_high;
						new_cluster = (new_cluster << 16) | (dirs[i].dir.DIR_first_cluster_low & 0xffff);

						// call recursive with next dir entry
						return GetClusterForDir(next_path_level, new_cluster);
					}

					// otherwise try next long name
					in_chain = 0;
					continue;

				}
				else
				{
					// something went wrong in this chain, drop out
					in_chain = 0;
					continue;
				}
			}
			else
			{
				// not in chain
				// check if it's a long name dir
				if(dirs[i].dir.DIR_attributes != ATTR_LONG_NAME)
				{
					// entry is a short name wihtout a long name entry or a volume ID entry
					// generate a long name from the short name
					char long_name_to_check[13];
					GetLongFromShort(&dirs[i].dir, long_name_to_check);

					// check it is the correct one (compare first l chars)
					int k;
					for(k = 0; k < l; k++)
					{
						// check char is equal
						if(long_name_to_check[k] != path[k])
						{
							break;
						}
					}

					// check if names match and it is a directory
					if(k == l  && long_name_to_check[k] == 0 && (dirs[i].dir.DIR_attributes & ATTR_DIRECTORY))
					{
						// is correct
						// get cluster number of this entry
						uint32_t new_cluster = dirs[i].dir.DIR_first_cluster_high;
						new_cluster = (new_cluster << 16) | (dirs[i].dir.DIR_first_cluster_low & 0xffff);

						// call recursive with next dir entry
						return GetClusterForDir(next_path_level, new_cluster);
					}

					continue;
				}
				// is it a long name dir entry, check if it is the start of a chain
				else if((dirs[i].long_name.LDIR_order & 0x40) == 0)
				{
					// is not the start of a long name entry - ignore it
					continue;
				}
				else
				{
					// is the start of a long-name chain
					// set up chain control vars
					// chain name is input backwards
					chain_name[255] = 0;
					chain_name_index = 254;
					chain_total = dirs[i].long_name.LDIR_order & 0x3f;
					chain_current = chain_total;
					in_chain = 1;

					// read in each of the 16-bit chars
					// name chars 3
					int j;
					for(j = 1; j >= 0; j--)
					{
						// get the char to check
						uint16_t c = dirs[i].long_name.LDIR_name3[j];

						// check for padding or null-char
						if(c == 0xffff || c == 0x0000)
						{
							continue;
						}
						else
						{
							chain_name[chain_name_index--] = (char)(c & 0xff);
						}
					}

					// name chars 2
					for(j = 5; j >= 0; j--)
					{
						// get the char to check
						uint16_t c = dirs[i].long_name.LDIR_name2[j];

						// check for padding or null-char
						if(c == 0xffff || c == 0x0000)
						{
							continue;
						}
						else
						{
							chain_name[chain_name_index--] = (char)(c & 0xff);
						}
					}

					// name chars 1
					for(j = 4; j >= 0; j--)
					{
						// get the char to check
						uint16_t c = dirs[i].long_name.LDIR_name1[j];

						// check for padding or null-char
						if(c == 0xffff || c == 0x0000)
						{
							continue;
						}
						else
						{
							chain_name[chain_name_index--] = (char)(c & 0xff);
						}
					}

					// have all name chars from this entry
					// decrease chain_current to point to next entry (or zero for last one)
					chain_current--;
				}
			}
		}


		// name was not found in this cluster
		// get sector of FAT table chunk and offset this cluster entry points to
		uint32_t FAT_sector = (cluster_to_search / (128 * 16)) * 16;	// 128 entries per sector, 16 sectors per chunk loaded
		uint32_t FAT_offset = cluster_to_search % (128 * 16);

		//printf("\nsector: %u (%u)", FAT_sector, FAT_area_start + FAT_sector);
		//printf("\nc sect: %u", current_FAT_sector_start);
		//printf("\noffset: %u", FAT_offset);
		//printf("\ncurrent cluster: %u", cluster_to_search);

		// check if that cluster chunk is already loaded
		if(FAT_sector != current_FAT_sector_start)
		{
			// it is not, load it
			AHCI_BOOT_READ((FAT_buffer - KOFFSET), FAT_area_start + FAT_sector, 16);
			current_FAT_sector_start = FAT_sector;
		}

		// get next cluster number
		uint32_t *clu = (uint32_t*)FAT_buffer;
		cluster_to_search = clu[FAT_offset] & 0x0fffffff;

		//printf("\nnext cluster to search: 0x%x %u", cluster_to_search, cluster_to_search);

		// check it for end of chain
		if(cluster_to_search >= 0x0ffffff7)
		{
			// reached end of the chain
			// this means that if we got this far, the directory entry does not exist
			break;
		}
	}

	// finished (shouldn't have got here - return 0 to indicate couldn't find directory)
	return 0;
}


// internal recursive function to find the starting cluster number for the topmost directory in "path"
static uint32_t GetClusterForFile(char *path, uint32_t cluster_to_search)
{
	// ignore leading '/' or '\'
	if(path[0] == '\\' || path[0] == '/')
	{
		path++;
	}

	// check if at end of path string
	if(path[0] == 0x00)
	{
		// are at end of it, return this cluster as the one to search in
		return cluster_to_search;
	}

	// get length of name to search for
	uint32_t l;
	for(l = 0; (path[l] != 0 && path[l] != '\\' && path[l] != '/'); l++);

	// generate pointer to next path level
	char *next_path_level = (char*)&(path[l]);

	// chain control vars
	uint32_t in_chain = 0;
	uint32_t chain_total = 0;
	uint32_t chain_current = 0;
	uint32_t chain_name_index = 0;
	char chain_name[256];

	// buffers and control vars
	char clu_buffer[8192];
	char FAT_buffer[8192];
	uint32_t current_FAT_sector_start = (cluster_to_search / (128 * 16)) * 16;

	// read in first chunk of FAT table
	AHCI_BOOT_READ((FAT_buffer - KOFFSET), FAT_area_start + current_FAT_sector_start, 16);

	// each cluster searched
	while(1)
	{
		// read in the data cluster
		uint64_t sector_start = ((cluster_to_search - 2) * sectors_per_cluster) + data_area_start;
		AHCI_BOOT_READ((clu_buffer - KOFFSET), sector_start, sectors_per_cluster);

		// search through each directory entry in this cluster
		union FAT32_DIR_LONG_NAME *dirs = (union FAT32_DIR_LONG_NAME*)clu_buffer;
		int i;
		int dir_count = sectors_per_cluster * 16;
		for(i = 0; i < dir_count; i++)
		{
			// check if entry is in use
			if(dirs[i].dir.DIR_name[0] == 0x00 || (dirs[i].dir.DIR_name[0] & 0xff) == 0xe5 || dirs[i].dir.DIR_attributes == ATTR_VOLUME_ID)
			{
				// entry not in use
				continue;
			}

			// entry is in use
			// check if currently in the middle of a long-name chain
			if(in_chain)
			{
				// is in the middle of a long name chain
				// check chain_current matches value in entry and that it is a long name entry
				if(dirs[i].long_name.LDIR_order == chain_current && dirs[i].long_name.LDIR_attributes == ATTR_LONG_NAME)
				{
					// is the next long name entry
					// get the chars
					// don't need to check for padding & null-char as these will only be present in the last long-name entry
					// name chars 3
					int j;
					for(j = 1; j >= 0; j--)
					{
						// get the char
						uint16_t c = dirs[i].long_name.LDIR_name3[j];
						chain_name[chain_name_index--] = (char)(c & 0xff);
					}

					// name chars 2
					for(j = 5; j >= 0; j--)
					{
						// get the char
						uint16_t c = dirs[i].long_name.LDIR_name2[j];
						chain_name[chain_name_index--] = (char)(c & 0xff);
					}

					// name chars 1
					for(j = 4; j >= 0; j--)
					{
						// get the char
						uint16_t c = dirs[i].long_name.LDIR_name1[j];
						chain_name[chain_name_index--] = (char)(c & 0xff);
					}

					// have all name chars from this entry
					// decrease chain_current to point to next entry (or zero for last one)
					chain_current--;
				}
				else if(chain_current == 0 && dirs[i].long_name.LDIR_attributes != ATTR_LONG_NAME)
				{
					// is the associated short name entry
					char *long_name_to_check = (char*)&(chain_name[chain_name_index + 1]);
					in_chain = 0;

					// check it is the correct one (compare first l chars)
					int k;
					for(k = 0; k < l; k++)
					{
						// check char is equal
						if(long_name_to_check[k] != path[k])
						{
							break;
						}
					}

					// check if names match
					if(k == l  && long_name_to_check[k] == 0)
					{
						// is correct
						// get cluster number of this entry
						uint32_t new_cluster = dirs[i].dir.DIR_first_cluster_high;
						new_cluster = (new_cluster << 16) | (dirs[i].dir.DIR_first_cluster_low & 0xffff);

						// call recursive with next dir entry
						return GetClusterForDir(next_path_level, new_cluster);
					}

					// otherwise try next long name
					in_chain = 0;
					continue;

				}
				else
				{
					// something went wrong in this chain, drop out
					in_chain = 0;
					continue;
				}
			}
			else
			{
				// not in chain
				// check if it's a long name dir
				if(dirs[i].dir.DIR_attributes != ATTR_LONG_NAME)
				{
					// entry is a short name wihtout a long name entry or a volume ID entry
					// generate a long name from the short name
					char long_name_to_check[13];
					GetLongFromShort(&dirs[i].dir, long_name_to_check);

					// check it is the correct one (compare first l chars)
					int k;
					for(k = 0; k < l; k++)
					{
						// check char is equal
						if(long_name_to_check[k] != path[k])
						{
							break;
						}
					}

					// check if names match
					if(k == l  && long_name_to_check[k] == 0)
					{
						// is correct
						// get cluster number of this entry
						uint32_t new_cluster = dirs[i].dir.DIR_first_cluster_high;
						new_cluster = (new_cluster << 16) | (dirs[i].dir.DIR_first_cluster_low & 0xffff);

						// call recursive with next dir entry
						return GetClusterForDir(next_path_level, new_cluster);
					}

					continue;
				}
				// is it a long name dir entry, check if it is the start of a chain
				else if((dirs[i].long_name.LDIR_order & 0x40) == 0)
				{
					// is not the start of a long name entry - ignore it
					continue;
				}
				else
				{
					// is the start of a long-name chain
					// set up chain control vars
					// chain name is input backwards
					chain_name[255] = 0;
					chain_name_index = 254;
					chain_total = dirs[i].long_name.LDIR_order & 0x3f;
					chain_current = chain_total;
					in_chain = 1;

					// read in each of the 16-bit chars
					// name chars 3
					int j;
					for(j = 1; j >= 0; j--)
					{
						// get the char to check
						uint16_t c = dirs[i].long_name.LDIR_name3[j];

						// check for padding or null-char
						if(c == 0xffff || c == 0x0000)
						{
							continue;
						}
						else
						{
							chain_name[chain_name_index--] = (char)(c & 0xff);
						}
					}

					// name chars 2
					for(j = 5; j >= 0; j--)
					{
						// get the char to check
						uint16_t c = dirs[i].long_name.LDIR_name2[j];

						// check for padding or null-char
						if(c == 0xffff || c == 0x0000)
						{
							continue;
						}
						else
						{
							chain_name[chain_name_index--] = (char)(c & 0xff);
						}
					}

					// name chars 1
					for(j = 4; j >= 0; j--)
					{
						// get the char to check
						uint16_t c = dirs[i].long_name.LDIR_name1[j];

						// check for padding or null-char
						if(c == 0xffff || c == 0x0000)
						{
							continue;
						}
						else
						{
							chain_name[chain_name_index--] = (char)(c & 0xff);
						}
					}

					// have all name chars from this entry
					// decrease chain_current to point to next entry (or zero for last one)
					chain_current--;
				}
			}
		}


		// name was not found in this cluster
		// get sector of FAT table chunk and offset this cluster entry points to
		uint32_t FAT_sector = (cluster_to_search / (128 * 16)) * 16;	// 128 entries per sector, 16 sectors per chunk loaded
		uint32_t FAT_offset = cluster_to_search % (128 * 16);

		//printf("\nsector: %u (%u)", FAT_sector, FAT_area_start + FAT_sector);
		//printf("\nc sect: %u", current_FAT_sector_start);
		//printf("\noffset: %u", FAT_offset);
		//printf("\ncurrent cluster: %u", cluster_to_search);

		// check if that cluster chunk is already loaded
		if(FAT_sector != current_FAT_sector_start)
		{
			// it is not, load it
			AHCI_BOOT_READ((FAT_buffer - KOFFSET), FAT_area_start + FAT_sector, 16);
			current_FAT_sector_start = FAT_sector;
		}

		// get next cluster number
		uint32_t *clu = (uint32_t*)FAT_buffer;
		cluster_to_search = clu[FAT_offset] & 0x0fffffff;

		//printf("\nnext cluster to search: 0x%x %u", cluster_to_search, cluster_to_search);

		// check it for end of chain
		if(cluster_to_search >= 0x0ffffff7)
		{
			// reached end of the chain
			// this means that if we got this far, the directory entry does not exist
			break;
		}
	}

	// finished (shouldn't have got here - return 0 to indicate couldn't find directory)
	return 0;
}


// path is a C-style null-terminated string containing the path of the directory to get entries for
// dirs_length is a value returned giving the length of the to be returned directory string
uint32_t BootGetDirCount(char *path, uint32_t *dirs_length)
{
	// get start cluster number for the directory
	uint32_t cluster_to_search = GetClusterForDir(path, root_cluster);

	// check if cluster is correct
	if(cluster_to_search == 0)
	{
		return FAT32_NO_PATH;
	}

	// search it for entries
	// entry count var & total name length var
	uint32_t entry_count;
	uint32_t total_name_length;

	// chain control vars
	uint32_t in_chain = 0;
	uint32_t chain_total = 0;
	uint32_t chain_current = 0;
	uint32_t chain_name_index = 0;
	char chain_name[256];

	// buffers and control vars
	char clu_buffer[8192];
	char FAT_buffer[8192];
	uint32_t current_FAT_sector_start = (cluster_to_search / (128 * 16)) * 16;

	// read in first chunk of FAT table
	AHCI_BOOT_READ((FAT_buffer - KOFFSET), FAT_area_start + current_FAT_sector_start, 16);

	// each cluster searched
	while(1)
	{
		// read in the data cluster
		uint64_t sector_start = ((cluster_to_search - 2) * sectors_per_cluster) + data_area_start;
		AHCI_BOOT_READ((clu_buffer - KOFFSET), sector_start, sectors_per_cluster);

		// search through each directory entry in this cluster
		union FAT32_DIR_LONG_NAME *dirs = (union FAT32_DIR_LONG_NAME*)clu_buffer;
		int i;
		int dir_count = sectors_per_cluster * 16;
		for(i = 0; i < dir_count; i++)
		{
			// check if entry is in use
			if(dirs[i].dir.DIR_name[0] == 0x00 || (dirs[i].dir.DIR_name[0] & 0xff) == 0xe5 || dirs[i].dir.DIR_attributes == ATTR_VOLUME_ID)
			{
				// entry not in use
				continue;
			}

			// entry is in use
			// check if currently in the middle of a long-name chain
			if(in_chain)
			{
				// is in the middle of a long name chain
				// check chain_current matches value in entry and that it is a long name entry
				if(dirs[i].long_name.LDIR_order == chain_current && dirs[i].long_name.LDIR_attributes == ATTR_LONG_NAME)
				{
					// is the next long name entry
					// get the chars
					// don't need to check for padding & null-char as these will only be present in the last long-name entry
					// name chars 3
					int j;
					for(j = 1; j >= 0; j--)
					{
						// get the char
						uint16_t c = dirs[i].long_name.LDIR_name3[j];
						chain_name[chain_name_index--] = (char)(c & 0xff);
					}

					// name chars 2
					for(j = 5; j >= 0; j--)
					{
						// get the char
						uint16_t c = dirs[i].long_name.LDIR_name2[j];
						chain_name[chain_name_index--] = (char)(c & 0xff);
					}

					// name chars 1
					for(j = 4; j >= 0; j--)
					{
						// get the char
						uint16_t c = dirs[i].long_name.LDIR_name1[j];
						chain_name[chain_name_index--] = (char)(c & 0xff);
					}

					// have all name chars from this entry
					// decrease chain_current to point to next entry (or zero for last one)
					chain_current--;
				}
				else if(chain_current == 0 && dirs[i].long_name.LDIR_attributes != ATTR_LONG_NAME)
				{
					// is the associated short name entry
					// get number of chars in name
					chain_name_index++;
					int k;
					for(k = 0; chain_name[k + chain_name_index] != 0; k++);

					// increase dir count
					total_name_length += k + 1; // includes semicolon terminator
					entry_count++;

					// move on to next entry
					in_chain = 0;
					continue;
				}
				else
				{
					// something went wrong in this chain, drop out
					in_chain = 0;
					continue;
				}
			}
			else
			{
				// not in chain
				// check if it's a long name dir
				if(dirs[i].dir.DIR_attributes != ATTR_LONG_NAME)
				{
					// entry is a short name wihtout a long name entry or a volume ID entry
					// generate a long name from the short name
					char long_name_to_check[13];
					GetLongFromShort(&dirs[i].dir, long_name_to_check);

					// get number of chars in name
					int k;
					for(k = 0; long_name_to_check[k] != 0; k++);

					// increase dir count
					total_name_length += k + 1; // includes semicolon terminator
					entry_count++;

					// move on to next entry
					continue;
				}
				// is it a long name dir entry, check if it is the start of a chain
				else if((dirs[i].long_name.LDIR_order & 0x40) == 0)
				{
					// is not the start of a long name entry - ignore it
					continue;
				}
				else
				{
					// is the start of a long-name chain
					// set up chain control vars
					// chain name is input backwards
					chain_name[255] = 0;
					chain_name_index = 254;
					chain_total = dirs[i].long_name.LDIR_order & 0x3f;
					chain_current = chain_total;
					in_chain = 1;

					// read in each of the 16-bit chars
					// name chars 3
					int j;
					for(j = 1; j >= 0; j--)
					{
						// get the char to check
						uint16_t c = dirs[i].long_name.LDIR_name3[j];

						// check for padding or null-char
						if(c == 0xffff || c == 0x0000)
						{
							continue;
						}
						else
						{
							chain_name[chain_name_index--] = (char)(c & 0xff);
						}
					}

					// name chars 2
					for(j = 5; j >= 0; j--)
					{
						// get the char to check
						uint16_t c = dirs[i].long_name.LDIR_name2[j];

						// check for padding or null-char
						if(c == 0xffff || c == 0x0000)
						{
							continue;
						}
						else
						{
							chain_name[chain_name_index--] = (char)(c & 0xff);
						}
					}

					// name chars 1
					for(j = 4; j >= 0; j--)
					{
						// get the char to check
						uint16_t c = dirs[i].long_name.LDIR_name1[j];

						// check for padding or null-char
						if(c == 0xffff || c == 0x0000)
						{
							continue;
						}
						else
						{
							chain_name[chain_name_index--] = (char)(c & 0xff);
						}
					}

					// have all name chars from this entry
					// decrease chain_current to point to next entry (or zero for last one)
					chain_current--;
				}
			}
		}


		// name was not found in this cluster
		// get sector of FAT table chunk and offset this cluster entry points to
		uint32_t FAT_sector = (cluster_to_search / (128 * 16)) * 16;	// 128 entries per sector, 16 sectors per chunk loaded
		uint32_t FAT_offset = cluster_to_search % (128 * 16);

		//printf("\nsector: %u (%u)", FAT_sector, FAT_area_start + FAT_sector);
		//printf("\nc sect: %u", current_FAT_sector_start);
		//printf("\noffset: %u", FAT_offset);
		//printf("\ncurrent cluster: %u", cluster_to_search);

		// check if that cluster chunk is already loaded
		if(FAT_sector != current_FAT_sector_start)
		{
			// it is not, load it
			AHCI_BOOT_READ((FAT_buffer - KOFFSET), FAT_area_start + FAT_sector, 16);
			current_FAT_sector_start = FAT_sector;
		}

		// get next cluster number
		uint32_t *clu = (uint32_t*)FAT_buffer;
		cluster_to_search = clu[FAT_offset] & 0x0fffffff;

		//printf("\nnext cluster to search: 0x%x %u", cluster_to_search, cluster_to_search);

		// check it for end of chain
		if(cluster_to_search >= 0x0ffffff7)
		{
			// reached end of the chain
			// this means that if we got this far, the directory entry does not exist
			break;
		}
	}

	// save total char count
	*dirs_length = total_name_length;

	// finished
	return FAT32_SUCCESS;
}

//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
// get dirs functino


uint32_t BootGetDirs(char *path, char *dirs_chars, uint32_t dirs_length)
{
	// get start cluster number for the directory
	uint32_t cluster_to_search = GetClusterForDir(path, root_cluster);

	// check if cluster is correct
	if(cluster_to_search == 0)
	{
		return FAT32_NO_PATH;
	}

	// search it for entries
	// entry count var & total name length var
	uint32_t dirs_index = 0;
	//uint32_t total_name_length;

	// chain control vars
	uint32_t in_chain = 0;
	uint32_t chain_total = 0;
	uint32_t chain_current = 0;
	uint32_t chain_name_index = 0;
	char chain_name[256];

	// buffers and control vars
	char clu_buffer[8192];
	char FAT_buffer[8192];
	uint32_t current_FAT_sector_start = (cluster_to_search / (128 * 16)) * 16;

	// read in first chunk of FAT table
	AHCI_BOOT_READ((FAT_buffer - KOFFSET), FAT_area_start + current_FAT_sector_start, 16);

	// each cluster searched
	while(1)
	{
		// read in the data cluster
		uint64_t sector_start = ((cluster_to_search - 2) * sectors_per_cluster) + data_area_start;
		AHCI_BOOT_READ((clu_buffer - KOFFSET), sector_start, sectors_per_cluster);

		// search through each directory entry in this cluster
		union FAT32_DIR_LONG_NAME *dirs = (union FAT32_DIR_LONG_NAME*)clu_buffer;
		int i;
		int dir_count = sectors_per_cluster * 16;
		for(i = 0; i < dir_count; i++)
		{
			// check if entry is in use
			if(dirs[i].dir.DIR_name[0] == 0x00 || (dirs[i].dir.DIR_name[0] & 0xff) == 0xe5 || dirs[i].dir.DIR_attributes == ATTR_VOLUME_ID)
			{
				// entry not in use
				continue;
			}

			// entry is in use
			// check if currently in the middle of a long-name chain
			if(in_chain)
			{
				// is in the middle of a long name chain
				// check chain_current matches value in entry and that it is a long name entry
				if(dirs[i].long_name.LDIR_order == chain_current && dirs[i].long_name.LDIR_attributes == ATTR_LONG_NAME)
				{
					// is the next long name entry
					// get the chars
					// don't need to check for padding & null-char as these will only be present in the last long-name entry
					// name chars 3
					int j;
					for(j = 1; j >= 0; j--)
					{
						// get the char
						uint16_t c = dirs[i].long_name.LDIR_name3[j];
						chain_name[chain_name_index--] = (char)(c & 0xff);
					}

					// name chars 2
					for(j = 5; j >= 0; j--)
					{
						// get the char
						uint16_t c = dirs[i].long_name.LDIR_name2[j];
						chain_name[chain_name_index--] = (char)(c & 0xff);
					}

					// name chars 1
					for(j = 4; j >= 0; j--)
					{
						// get the char
						uint16_t c = dirs[i].long_name.LDIR_name1[j];
						chain_name[chain_name_index--] = (char)(c & 0xff);
					}

					// have all name chars from this entry
					// decrease chain_current to point to next entry (or zero for last one)
					chain_current--;
				}
				else if(chain_current == 0 && dirs[i].long_name.LDIR_attributes != ATTR_LONG_NAME)
				{
					// is the associated short name entry
					// copy the dir name into the array
					for(chain_name_index++; (chain_name[chain_name_index] != 0) && (dirs_index < dirs_length); chain_name_index++, dirs_index++)
					{
						dirs_chars[dirs_index] = chain_name[chain_name_index];
					}

					if(chain_name[chain_name_index] != 0)
					{
						// the directory make up changed between this function and the call to the get name length function
						return FAT32_DIR_CHANGE;
					}

					// append a semicolon ';'
					if(dirs_index < dirs_length)
					{
						dirs_chars[dirs_index++] = ';';
					}

					// move on to next entry
					in_chain = 0;
					continue;
				}
				else
				{
					// something went wrong in this chain, drop out
					in_chain = 0;
					continue;
				}
			}
			else
			{
				// not in chain
				// check if it's a long name dir
				if(dirs[i].dir.DIR_attributes != ATTR_LONG_NAME)
				{
					// entry is a short name wihtout a long name entry or a volume ID entry
					// generate a long name from the short name
					char long_name_to_check[13];
					GetLongFromShort(&dirs[i].dir, long_name_to_check);

					// copy the dir name into the array
					int k;
					for(k = 0; (long_name_to_check[k] != 0) && (dirs_index < dirs_length); dirs_index++, k++)
					{
						dirs_chars[dirs_index] = long_name_to_check[k];
					}

					if(long_name_to_check[k] != 0)
					{
						// the directory make up changed between this function and the call to the get name length function
						return FAT32_DIR_CHANGE;
					}

					// append a semicolon ';'
					if(dirs_index < dirs_length)
					{
						dirs_chars[dirs_index++] = ';';
					}

					// move on to next entry
					continue;
				}
				// is it a long name dir entry, check if it is the start of a chain
				else if((dirs[i].long_name.LDIR_order & 0x40) == 0)
				{
					// is not the start of a long name entry - ignore it
					continue;
				}
				else
				{
					// is the start of a long-name chain
					// set up chain control vars
					// chain name is input backwards
					chain_name[255] = 0;
					chain_name_index = 254;
					chain_total = dirs[i].long_name.LDIR_order & 0x3f;
					chain_current = chain_total;
					in_chain = 1;

					// read in each of the 16-bit chars
					// name chars 3
					int j;
					for(j = 1; j >= 0; j--)
					{
						// get the char to check
						uint16_t c = dirs[i].long_name.LDIR_name3[j];

						// check for padding or null-char
						if(c == 0xffff || c == 0x0000)
						{
							continue;
						}
						else
						{
							chain_name[chain_name_index--] = (char)(c & 0xff);
						}
					}

					// name chars 2
					for(j = 5; j >= 0; j--)
					{
						// get the char to check
						uint16_t c = dirs[i].long_name.LDIR_name2[j];

						// check for padding or null-char
						if(c == 0xffff || c == 0x0000)
						{
							continue;
						}
						else
						{
							chain_name[chain_name_index--] = (char)(c & 0xff);
						}
					}

					// name chars 1
					for(j = 4; j >= 0; j--)
					{
						// get the char to check
						uint16_t c = dirs[i].long_name.LDIR_name1[j];

						// check for padding or null-char
						if(c == 0xffff || c == 0x0000)
						{
							continue;
						}
						else
						{
							chain_name[chain_name_index--] = (char)(c & 0xff);
						}
					}

					// have all name chars from this entry
					// decrease chain_current to point to next entry (or zero for last one)
					chain_current--;
				}
			}
		}


		// name was not found in this cluster
		// get sector of FAT table chunk and offset this cluster entry points to
		uint32_t FAT_sector = (cluster_to_search / (128 * 16)) * 16;	// 128 entries per sector, 16 sectors per chunk loaded
		uint32_t FAT_offset = cluster_to_search % (128 * 16);

		//printf("\nsector: %u (%u)", FAT_sector, FAT_area_start + FAT_sector);
		//printf("\nc sect: %u", current_FAT_sector_start);
		//printf("\noffset: %u", FAT_offset);
		//printf("\ncurrent cluster: %u", cluster_to_search);

		// check if that cluster chunk is already loaded
		if(FAT_sector != current_FAT_sector_start)
		{
			// it is not, load it
			AHCI_BOOT_READ((FAT_buffer - KOFFSET), FAT_area_start + FAT_sector, 16);
			current_FAT_sector_start = FAT_sector;
		}

		// get next cluster number
		uint32_t *clu = (uint32_t*)FAT_buffer;
		cluster_to_search = clu[FAT_offset] & 0x0fffffff;

		//printf("\nnext cluster to search: 0x%x %u", cluster_to_search, cluster_to_search);

		// check it for end of chain
		if(cluster_to_search >= 0x0ffffff7)
		{
			// reached end of the chain
			// this means that if we got this far, the directory entry does not exist
			break;
		}
	}

	// append finishing null-terminator
	if(dirs_index < dirs_length)
	{
		dirs_chars[dirs_index++] = 0x00;
	}
	else
	{
		return FAT32_DIR_CHANGE;
	}

	// finished
	return FAT32_SUCCESS;
}


uint32_t BootReadFile(char *path, char *buffer, uint32_t start, uint32_t length)
{
	// get start cluster for file
	uint32_t start_cluster = GetClusterForFile(path, root_cluster);

	// check for 0
	if(start_cluster == 0)
	{
		return FAT32_NO_PATH;
	}

	// found the first cluster
	// read it in
	char clu_buffer[8192];
	uint64_t sector_start = ((start_cluster - 2) * sectors_per_cluster) + data_area_start;
	AHCI_BOOT_READ((clu_buffer - KOFFSET), sector_start, sectors_per_cluster);

	// copy data to buffer
	int i;
	for(i = 0; i < length; i++)
	{
		buffer[i] = clu_buffer[i];
	}



	return FAT32_SUCCESS;
}











